feat(objectql): publish DriverConnect, DatasourceUnavailable and SummaryRecompute error codes as constants - #16308
Conversation
…rror codes as constants Both classes' own docblocks already say the refusal is "Identified by `code` rather than `instanceof` so it survives crossing package boundaries", and neither offered anything to import. Following that published instruction meant re-spelling the wire string in the consumer's own package -- a `check:error-code-provenance` stamp site there, free to drift from what this engine throws with no compile error to say so. `packages/rest/src/error-response.ts` does exactly that for the datasource refusal today. `DRIVER_CONNECT_CODE` and `DATASOURCE_UNAVAILABLE_CODE` are new exports from `@objectstack/objectql`, re-exported from `index.ts` beside the classes they name. Dropping the `ERR_` prefix from the constants' NAMES follows this package's existing precedents (`HOOK_TARGET_REBIND_ERROR_CODE`, `READONLY_FIELD_REJECTED_CODE`). Both strings are byte-identical to the literals they replace: each quoted spelling occurs exactly once in the file on both sides of the change -- it moved, it did not multiply or mutate. Both classes are ALSO published from the lean `./core` entry while the constants, like every other `*_CODE` here, are batteries-only. That asymmetry is #16260's subject for the whole family and is deliberately not decided by this mechanical conversion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
`SummaryRecomputeError`'s docblock already says it is "Identified by `code` rather than `instanceof` so it survives crossing package boundaries", and offered nothing to import. This row's cost is the batch's most concrete: TWO first-party packages already re-spell the wire string, and both do it to implement the very recovery the class was designed for -- "the triggering records WERE written, so treat a failed roll-up as a warning and keep them": `packages/rest/src/import-runner.ts` and `packages/metadata-protocol/src/seed-loader.ts`. Three spellings of one code across three packages, kept equal by nothing but a grep. `SUMMARY_RECOMPUTE_CODE` is a new export from `@objectstack/objectql`, re-exported from `index.ts` beside the class, which is where that class is already published. Naming and field spelling follow this package's precedents. The string is byte-identical to the literal it replaces: the quoted spelling occurs exactly once in the file on both sides of the change. This commit does NOT rewire the two consumers named above -- that is a consumer-side change in two other packages, outside a producer-side sweep, and no gate asks for it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
One changeset, three graded entries -- batching these rows into one PR changed how many PRs the sweep costs, not how each row is graded. Each of `DRIVER_CONNECT_CODE`, `DATASOURCE_UNAVAILABLE_CODE` and `SUMMARY_RECOMPUTE_CODE` is additive widening of a published surface with nothing removed, which is `minor` on its own account. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 16 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9e341eb6dba14ebc7e8a32883b54cbfa69705659 && git checkout 9e341eb6dba14ebc7e8a32883b54cbfa69705659
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b9a14dd64f0e579adf94d683eda729991f6935e0 97002640edbe386774c4b780d1c7ad0a9a4ab7e8 && git checkout -B drift-repro b9a14dd64f0e579adf94d683eda729991f6935e0 && git merge --no-ff 97002640edbe386774c4b780d1c7ad0a9a4ab7e8
node scripts/docs-audit/affected-docs.mjs --json b9a14dd64f0e579adf94d683eda729991f6935e0
|
|
The consumer-side follow-up this PR's body promises but deliberately does not do is filed as #16309 — the three first-party call sites that re-spell these codes today (
Generated by Claude Code |
Each of the three new `*_CODE` constants sat BETWEEN its class's docblock and the class. Two consecutive JSDoc blocks both attach to the declaration that follows, so the emitted declarations carried both blocks on the CONSTANT and `declare class ...Error` shipped undocumented — measured on the package's own `tsup` emitter, not inferred. Move each constant and its own docblock ABOVE the class docblock, the grouped shape `registry.ts` already uses. Pure line reordering: the sorted line multiset of both files is byte-identical to the previous commit's, and the exported name set of every declaration file `files[]` publishes is unchanged. Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Follow-up commit Defect. Each new Before — emitted After — same file, rebuilt:
Fix. Each constant and its own docblock moved ABOVE the class docblock — the grouped shape Proof it is only a reordering.
Checks. Still a draft; Generated by Claude Code |
Part of #16159
Batch i of that card's sweep, per the batching ruling recorded on it: three rows of its table, in two files. Each of
DriverConnectError,DatasourceUnavailableErrorandSummaryRecomputeErrornow publishes its ADR-0112codeas an importable constant, matching the shape set by #16283 and #16259.⛔ Deliberately NOT in this PR, and untouched:
packages/objectql/src/transaction-errors.ts(2 rows — batch ii, serialised behind this PR because both edit the barrel and the repo runs a single-writer-path check),packages/objectql/src/validation/record-validator.ts(1 row — held untilVALIDATION_FAILEDvsVALIDATION_ERRORis ruled), andcontent/docs/kernel/contracts/data-engine.mdx(#16281 tracks the doc sweep; publishing a constant makes that page's sentence true, not false).Why these three rows
Each of the three classes already tells its reader, in its own docblock, that it is "Identified by
coderather thaninstanceofso it survives crossing package boundaries" — and offered nothing to import. The only way to FOLLOW that published instruction was to re-spell the wire string in the consumer's own package, which acquires acheck:error-code-provenancestamp site there and is then free to drift from what the engine throws with no compile error to say so.That cost is already shipped, not hypothetical. Three first-party packages in this repo match these refusals by
codetoday, and therefore carry a second spelling of the string:packages/rest/src/error-response.ts:1024ERR_DATASOURCE_UNAVAILABLEpackages/rest/src/import-runner.ts:537ERR_SUMMARY_RECOMPUTEpackages/metadata-protocol/src/seed-loader.ts:2081ERR_SUMMARY_RECOMPUTE⛔ This PR does not rewire those three call sites. That is a consumer-side change in two other packages, outside a producer-side sweep, and no gate asks for it. It is recorded here as the evidence for the card, and is filed separately as a follow-up.
Clause ② — graded per row, from a measurement
Batching changed how many PRs the sweep costs, ⛔ not how each row is graded. The instrument is the one this repo's rounds converged on: build at head → swap the changed sources back to their base → rebuild → compare exported NAME SETS across every declaration file
files[]publishes (⛔ not bytes:tsuprenumbers internal aliases and the shared chunk's filename is content-hashed, so a byte delta is dominated by noise that is not a surface change).dist/index.d.tsdist/index.d.mtsdist/core.d.ts/.d.mtsutil-*.d.ts/.d.mtsbf,bg) ⇒ correctly NOT surfacedriver-connect-errors.ts—ERR_DRIVER_CONNECTDRIVER_CONNECT_CODEminordriver-connect-errors.ts—ERR_DATASOURCE_UNAVAILABLEDATASOURCE_UNAVAILABLE_CODEminorsummary-errors.ts—ERR_SUMMARY_RECOMPUTESUMMARY_RECOMPUTE_CODEminorThe three rows are separable in that measurement rather than lumped: each constant appears by name in the ADDED set, and
REMOVEDis empty in both realms.⭐ Two of the three constants reach
index.d.tsonly through the shared chunk (aB as DATASOURCE_UNAVAILABLE_CODE,aE as DRIVER_CONNECT_CODE), whileSUMMARY_RECOMPUTE_CODEis declared inindex.d.tsdirectly. Counts, with a negative control at 0 on both sides: chunk0 → 5and0 → 2for the first two,0 → 0for the third;index.d.ts0 → 1,0 → 1,0 → 3. The barrel's re-export line names all three either way, so the name-set reading holds — but the census was taken over every published declaration, not the root barrel alone.Ablation hygiene, since these numbers depend on it: implementation committed first; mutation proven on disk before measuring (each path's
git hash-objectequal to the BASE blob, plus whole-line anchored greps at 0/0/0 for the new constants and 1/1/1 for the literals they replace); objectql rebuilt on each leg withtsupinvoked directly, no turbo cache on the path, proven bydist/index.d.tsmtime moving1788698128 → 1788698599 → 1788698619; restore proven byte-exact (git hash-objectequal to the HEAD blob for all three paths,git diff HEADempty,git status --porcelainempty ⇒ zero blast radius); and the restored build's six declaration files are byte-identical hashes to the head snapshot, so the base leg was a measurement rather than a blind spot.The two inverted gates, measured rather than predicted
All three codes are registered in
ERROR_CODE_LEDGERunder@objectstack/objectql— like #16283's row and unlike #16259's. The prediction that follows was checked, not assumed:check:error-code-provenanceis NOT neutral.311 → 314registered-code stamp sites,295 → 298listed, waivers unchanged at 16, exit 0 on both sides. Exactly +1 per row: each newconstdefis a stamp site this gate sees, and it passes because the code is listed under this package's own owner key.check:dispatcher-error-vocabularydoes not move. Verdict and scope lines are byte-identical either side (66 unregistered code-stamping sites, all classified; 2371 files; 303 registered codes), exit 0 both. That table records UNREGISTERED sites, so a registered code is invisible to it by construction — and no row inpackages/runtime/src/dispatcher-error-vocabulary.tschanges. No cross-package edit is owed for any of these three rows.Shape
Follows #16283 exactly:
export const X_CODE = '…' as const;immediately above its class, the class field as a barereadonly code = X_CODE;(⛔ noas conston the FIELD — that spelling is whatcheck:dispatcher-error-vocabularyclassifies asclassconst), re-exported fromindex.tsbeside the class. Each string is byte-identical to the literal it replaces: the quoted wire spelling occurs exactly once per code in its file on both sides of the change — it moved, it did not multiply or mutate.DriverConnectErrorandDatasourceUnavailableErrorare published from the lean./coreentry as well as from the batteries barrel, while these constants — like every other*_CODEin this package — go on the batteries barrel only. That is #16260's subject for the whole family; deciding it inside a mechanical sweep is what slicing was meant to prevent. A reviewer who wants it decided here can say so cheaply: it would be two more lines incore.ts.Verification
driver-connect-code-constant.test.ts,datasource-unavailable-code-constant.test.ts,summary-recompute-code-constant.test.ts), five cases each: the literal wire string (the byte-identity fence, deliberately NOT a constant compare — a pin that reads the constant cannot catch the constant being wrong), the code a real refusal carries asserted withnameand the discriminating payload (⛔ never a baretoThrow()), barrel reachability by identity, barrel-class/barrel-constant agreement, and a cross-realm copy whereinstanceofisfalsewhile thecodecompare holds.statusfield, so ADR-0112'scode+statusminimum reduces tocodeplus the discriminating fields; ⛔ inventing astatuswould be new published surface and is not what this card converts.pnpm --filter @objectstack/objectql exec vitest runover those three plus six existing suites that exercise these refusals — 9 files / 69 tests passed.pnpm --filter @objectstack/objectql typecheck— exit 0, includingcheck:test-typecheck: the test-layer debt ledger holds at 44 files / 242 errors / 69 pinned signatures, unchanged. The three new test files are proven inside that program bytsc --noEmit --listFiles -p tsconfig.test.json(1 hit each, firing control 1, negative control 0) and contribute 0 of the 242 — that count is the firing control on the same command.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(7 paths vs merge base2756e07d1), 101 runnable commands, 99 exit 0. The two that did not are NOT MEASURED rather than red, and neither is about this diff:check:dual-build-cjs-loadsandcheck:published-readme-exportsreturned exit 3 PREREQUISITE NOT MET (they read every package'sdist/), andcheck:react-declaration-parityprinted "MANIFEST is not set — this gate did NOT run" (it needs an objectui build and a browser). Two further gates print NOT WIRED without a PR number and are re-run against this PR below.Re-runs, and the head every reading above was taken at
⭐ Every gate reading in this body was taken at
0f1b41393, which is this branch's head; nothing has been pushed since.check:dual-build-cjs-loadsexit 0 — 103 publishedrequireentry points across 66 packages, 619 emitted CommonJS files parse (its population is read frompackages/**/package.jsondeclaring therequirecondition, and this package declares it on both.and./core);check:published-readme-exportsexit 0 — 60 documents across 79 workspace packages.check:dts-closure's FIRST green was about a population that mostly excluded this tree: 15 built packages swept, because only the objectql dependency closure had been built. Re-run after the workspace build: 72 built packages, 164/164 declared declaration files present, exit 0. Quoting the first reading as evidence would have been the vacuous-green shape.check:partof-closing-keyword, re-run with the posted body rather than an empty environment (it readsPR_BODYand passes vacuously when that is unset): exit 0, "PR feat(objectql): publish DriverConnect, DatasourceUnavailable and SummaryRecompute error codes as constants #16308 carries no Part-of/closing-keyword contradiction."check:single-claim-paths, re-run with this PR's number: exit 0.SINGLE_CLAIM_PATHSlist holds exactly one entry,.objectui-sha, and its own docblock says a shared registry or root manifest deliberately does NOT belong there. Sopackages/objectql/src/index.tsis not a gate-declared at-most-one-writer path, and this gate will not serialise batch ii behind this PR. Keeping the two batches serial is still right — both edit adjacent regions of one barrel and the second would likely take a textual merge conflict — but it is a merge-hygiene choice, not a mechanised one.pnpm lint(eslint . --no-inline-config) run IN FULL, exit 0 — ⛔ no narrowing claimed. Non-vacuity control: re-running eslint over exactly the six changed/added files with--format jsonreports 6 files linted, 0 errors, 0 warnings, so the repo-wide green demonstrably covers them.node scripts/pm/check-clause2-carriers.mjs --pair 16308— exit 0 (captured by redirect-then-read, ⛔ never through a pipe): "the clause-② declaration is readable in the fixed spelling and both carriers agree." The checker was proved current first: its blob hash equalsorigin/main's.needs:contract-reviewis hung on both carriers (this PR and objectql: 11 error classes still spell their code as an inline literal, so a consumer cannot follow theby code, not instanceofconvention the docs already teach #16159) through the additive endpoint, with a comparative read-back provingunion(read, target)exactly and nothing stripped — the auto-labeler's four survived. ⛔ Not released and not cleared by this round: a separate reviewing seat owns that.🤖 Generated with Claude Code
https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
Generated by Claude Code
Generated by Claude Code